package example.utils;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Clock;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.impl.DefaultClock;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;

/**
 * TODO
 * 中农裕坤 JWT 生成 和 解析 和 验证
 *
 * <dependency>
 * <groupId>io.jsonwebtoken</groupId>
 * <artifactId>jjwt</artifactId>
 * <version>0.9.1</version>
 * </dependency>
 *
 * <dependency>
 * <groupId>com.auth0</groupId>
 * <artifactId>java-jwt</artifactId>
 * <version>3.9.0</version>
 * </dependency>
 *
 * @author zgq
 * @date 2020/10/22 10:55
 */
@Slf4j
@Component
public class Jwts_JWTUtil {
    /**
     * 盐
     */
    private final String secret = "zhang";
    /**
     * 失效时间 2 小时
     */
    private final Long expire = 72000000L;
    /**
     * 时钟
     */
    Clock clock = DefaultClock.INSTANCE;


    /**
     * 生成 Token
     *
     * @param userDetails 登录账号
     * @return token
     */
    public String generateToken(UserDetails userDetails) {
        Map<String, Object> claims = new HashMap<>();
        return doGenerateToken(claims, userDetails.getUsername());
    }

    /**
     * 生成 Token
     *
     * @param claims    自定义要求规范
     * @param loginName 登录名称
     * @return token
     */
    public String doGenerateToken(Map<String, Object> claims, String loginName) {
        return Jwts.builder()
                // 设置自定义要求规范
                .addClaims(claims)
                // 登录人的 ID
                .setId("1001")
                // 设置登陆人
                .setSubject(loginName)
                // 设置 Token 的开始时间
                .setIssuedAt(clock.now())
                // 过期时间 30 秒
                .setExpiration(new Date(System.currentTimeMillis() + 30000))
                // 2小时
                //.setExpiration(new Date(System.currentTimeMillis() + expire))
                // 设置加密方式
                .signWith(SignatureAlgorithm.HS256, secret)
                .compact();
    }


    /**
     * 解析token
     *
     * @param token 入参 token
     */
    public Claims parseToken(String token) {
        try {
            Claims body = Jwts.parser()
                    // 盐
                    .setSigningKey(secret)
                    .parseClaimsJws(token)
                    .getBody();

            String id = body.getId();
            String subject = body.getSubject();
            Date issuedAt = body.getIssuedAt();
            Date expiration = body.getExpiration();
            log.info("登录ID:{}", id);
            log.info("登录姓名:{}", subject);
            log.info("登录时间:{}", issuedAt);
            log.info("登录过期时间:{}", expiration);
            return body;
        } catch (Exception ex) {
            ex.printStackTrace();
            throw new RuntimeException("登录凭证已经过期，请重新登录");
        }

    }

    /**
     * 认证 token
     *
     * @param token String
     * @return Boolean
     */
    public Boolean verifyToken(String token, String loginName) {
        try {
            Claims claims = parseToken(token);
            String userName = claims.getSubject();
            return StringUtils.equalsIgnoreCase(userName, loginName) && isTokenExpired(token);
        } catch (Exception ex) {
            ex.printStackTrace();
            throw new RuntimeException("登录凭证已经过期，请重新登录");
        }
    }

    /**
     * 判断token时间是否已经失效
     */
    public Boolean isTokenExpired(String token) {
        final Date expiration = getExpirationDateFromToken(token, Claims::getExpiration);
        // 失效时间在当前时间之后
        boolean after = expiration.after(clock.now());
        return after;
    }

    public <T> T getExpirationDateFromToken(String token, Function<Claims, T> claims) {
        Claims parseToken = parseToken(token);
        return claims.apply(parseToken);
    }

    public String getUserNameFromToke(String authToken) {
        return getExpirationDateFromToken(authToken, Claims::getSubject);
    }
}
